/// Unsafe for MultiThreads

#pragma once
#include <typeinfo>
#include <stdexcept>

class Reference
{
    int _refCount;
    template <typename T> friend class ref_ptr;
public:
    Reference() : _refCount(0) {}

protected:
    virtual ~Reference()
    {
        if (_refCount>0)
        {
            //std::cout << "Warning: deleting still referenced object " << this << " of type '" << typeid(this).name() << "'" << std::endl;
            //std::cout << "         the final reference count was " << _refCount << ", memory corruption possible." << std::endl;
        }
    }

    void ref() { ++_refCount; }

    void unref()
    {
        if (--_refCount <= 0)
            delete this;
    }
};

template<typename T>
class ref_ptr
{
public:
    ref_ptr() : _ptr(nullptr) { }

    ref_ptr(T* ptr) : _ptr(ptr) { if (_ptr) _ptr->ref(); }

    ref_ptr(const ref_ptr& r) : _ptr(r._ptr) { if (_ptr) _ptr->ref(); }

    template<class Other> ref_ptr(const ref_ptr<Other>& r) : _ptr(r._ptr) { if (_ptr) _ptr->ref(); }

    ~ref_ptr() { if (_ptr) _ptr->unref();  _ptr = nullptr; }

    ref_ptr& operator=(T* ptr)
    {
        if (_ptr == ptr) return *this;
        T* tmp_ptr = _ptr;
        _ptr = ptr;
        if (_ptr) _ptr->ref();
        if (tmp_ptr) tmp_ptr->unref();
        return *this;
    }

    ref_ptr& operator=(const ref_ptr& r) { assign(r); return *this; }

    template<class Other> ref_ptr& operator=(const ref_ptr<Other>& r) { assign(r); return *this; }

    T* get() const { return _ptr; }

    operator T*() const { return _ptr; }

    bool valid() const { return _ptr != nullptr; }

    bool operator!() const { return _ptr == nullptr; }

    T* operator->() const { return _ptr; }

    T& operator*() const
    {
        if (!_ptr)
            throw std::runtime_error(typeid(T).name());
        return *_ptr;
    }

private:
    template<class Other>
    void assign(const ref_ptr<Other>& r)
    {
        if (_ptr == r._ptr) return;
        T* tmp_ptr = _ptr;
        _ptr = r._ptr;
        if (_ptr) _ptr->ref();
        // unref second to prevent any deletion of any object which might
        // be referenced by the other object. i.e rp is child of the
        // original _ptr.
        if (tmp_ptr) tmp_ptr->unref();
    }

    T* _ptr;

    template<class Other> friend class ref_ptr;
};
